<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  
  <link href="../css/style.css" rel="stylesheet" type="text/css">

</head>
<body>
<h1>4.6. Сложение и вычитание с переносом</h1>
<p class="article">
В системе команд процессоров x86 имеются специальные команды сложения и вычитания с учётом флага переноса (CF). Для сложения с учётом переноса предназначена команда ADC, а для вычитания — SBB. В общем, эти команды работают почти также, как ADD и SUB, единственное отличие в том, что к младшему разряду первого операнда прибавляется или вычитается дополнительно значение флага CF.
Зачем нужны такие команды? Они позволяют выполнять сложение и вычитание многобайтных целых чисел, длина которых больше, чем разрядность регистров процессора (в нашем случае 16 бит). Принцип программирования таких операций очень прост — длинные числа складываются (вычитаются) по частям. Младшие разряды складываются(вычитаются) с помощью обычных команд ADD и SUB, а затем последовательно складываются(вычитаются) более старшие части с помощью команд ADC и SBB. Так как эти команды учитывают перенос из старшего разряда, то мы можем быть уверены, что ни один бит не потеряется. Этот способ похож на сложение(вычитание) десятичных чисел в столбик.
</p>
<div class="image">
	<img src="../img/4.6_1.png" />
	<p>Сложение командой ADD</p>
</div>
<p class="article">
При сложении происходит перенос из 7-го разряда в 8-й, как раз на границе между байтами. Если мы будем складывать эти числа по частям командой ADD, то перенесённый бит потеряется и в результате мы получим ошибку. К счастью, перенос из старшего разряда всегда сохраняется в флаге CF. Чтобы прибавить этот перенесённый бит, достаточно применить команду ADC:
</p>
<div class="image">
	<img src="../img/4.6_2.png" />
	<p>Отдельное сложение младших и старших частей длинного числа</p>
</div>
<p class="article">
Аналогичная ситуация возникает с вычитанием чисел по частям. Чтобы было совсем понятно, приведу пример программы. Допустим, требуется вычислить значение формулы k=i+j-n+1, где переменные k, i, j и n являются 32-битными целыми числами без знака. Складывать и вычитать такие числа придётся в два этапа: сначала вычисления будут производиться с младшими словами операндов, а затем со старшими с учётом переноса.
Для прибавления единицы в данном примере нельзя использовать команду INC, так как она не влияет на флаг CF и мы можем получить ошибку в результате!
</p>
<pre class="code">
use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h
 
    mov ax,word[i]    ;Загружаем младшую часть i в AX
    mov bx,word[i+2]  ;Загружаем старшую часть i в BX
 
    add ax,word[j]    ;Складываем младшие части i и j
    adc bx,word[j+2]  ;Складываем старшие части i и j
 
    sub ax,word[n]
    sbb bx,word[n+2]  ;BX:AX = i+j-n
 
    add ax,1          ;Команда INC здесь не подходит!
    adc bx,0          ;BX:AX = i+j-n+1
 
    mov word[k],ax    ;\
    mov word[k+2],bx  ;/ Сохраняем результат в k
 
    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
i dd 120000
j dd  80500
n dd   2300
k dd      ?
</pre>
<p class="article">
Запись word[i] означает, что мы переопределяем размер переменной (она объявлена как DWORD) и обращаемся к младшему слову. Старшее слово расположено в памяти после младшего, поэтому к адресу переменной надо прибавить 2, и соответствующая запись будет иметь вид word[i+2].
Посмотреть работу программы можно в отладчике.
</p>
<p class="article">
Обратите внимание, как хранятся переменные в памяти. В процессорах Intel младший байт всегда хранится по младшему адресу, поэтому получается, что в окне дампа значения надо читать справа налево. В регистрах же числа записываются в нормальном виде. Сравните, как выглядит одно и то же значение k в памяти и в регистрах (старшая часть находится в BX, а младшая — в AX).
Одно из преимуществ ассемблера в том, что на нём можно реализовать работу с собственными форматами чисел, например с очень длинными целыми. А в языках высокого уровня выбор всегда ограничен компилятором. Следующая программа складывает два 7-байтных значения (для разнообразия я использовал только один регистр).
</p>
<pre class="code">
use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h
 
    mov ax,word[x]
    add ax,word[y]
    mov word[z],ax
 
    mov ax,word[x+2]
    adc ax,word[y+2]
    mov word[z+2],ax
 
    mov ax,word[x+4]
    adc ax,word[y+4]
    mov word[z+4],ax
 
    mov al,byte[x+6]
    adc al,byte[y+6]
    mov byte[z+6],al
 
    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
x dd 0xF1111111
  dw 0xF111
  db 0x11
y dd 0x22222222
  dw 0x2222
  db 0x22
z rb 7
</pre>
<p class="article">
Обращение к старшему байту записывается как byte[x+6]. Команда MOV не меняет состояние флагов, поэтому её можно ставить между командами сложения.
</p>
</body>
</html>
